libobs_wrapper\display\window_manager/
mod.rs

1//! This
2
3use std::sync::{
4    atomic::{AtomicBool, Ordering},
5    Arc, Mutex,
6};
7
8use lazy_static::lazy_static;
9use libobs::obs_display_t;
10use windows::{
11    core::{w, HSTRING, PCWSTR},
12    Win32::{
13        Foundation::{COLORREF, HWND, LPARAM, LRESULT, WPARAM},
14        Graphics::Dwm::DwmIsCompositionEnabled,
15        System::{
16            LibraryLoader::{GetModuleHandleA, GetModuleHandleW},
17            SystemInformation::{GetVersionExW, OSVERSIONINFOW},
18        },
19        UI::WindowsAndMessaging::{
20            CreateWindowExW, DefWindowProcW, DispatchMessageW, GetMessageW, GetWindowLongPtrW,
21            LoadCursorW, PostMessageW, PostQuitMessage, RegisterClassExW,
22            SetLayeredWindowAttributes, SetParent, SetWindowLongPtrW, TranslateMessage, CS_HREDRAW,
23            CS_NOCLOSE, CS_OWNDC, CS_VREDRAW, GWL_EXSTYLE, GWL_STYLE, HTTRANSPARENT, IDC_ARROW,
24            LWA_ALPHA, MSG, WM_NCHITTEST, WNDCLASSEXW, WS_CHILD, WS_EX_COMPOSITED, WS_EX_LAYERED,
25            WS_EX_TRANSPARENT, WS_POPUP, WS_VISIBLE,
26        },
27    },
28};
29
30use crate::unsafe_send::Sendable;
31
32mod position_trait;
33mod show_hide;
34mod misc;
35pub use misc::*;
36pub use position_trait::WindowPositionTrait;
37pub use show_hide::ShowHideTrait;
38
39const WM_DESTROY_WINDOW: u32 = 0x8001; // Custom message
40extern "system" fn wndproc(
41    window: HWND,
42    message: u32,
43    w_param: WPARAM,
44    l_param: LPARAM,
45) -> LRESULT {
46    unsafe {
47        match message {
48            WM_NCHITTEST => {
49                return LRESULT(HTTRANSPARENT as _);
50            }
51            WM_DESTROY_WINDOW => {
52                PostQuitMessage(0);
53                return LRESULT(0);
54            }
55            _ => {
56                return DefWindowProcW(window, message, w_param, l_param);
57            }
58        }
59    }
60}
61
62//TODO generated by AI, check later
63fn is_windows8_or_greater() -> windows::core::Result<bool> {
64    let mut os_info: OSVERSIONINFOW = unsafe { std::mem::zeroed() };
65    os_info.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOW>() as u32;
66
67    unsafe {
68        GetVersionExW(&mut os_info)?;
69    }
70
71    let r = (os_info.dwMajorVersion > 6)
72        || (os_info.dwMajorVersion == 6 && os_info.dwMinorVersion >= 2);
73    Ok(r)
74}
75
76lazy_static! {
77    static ref REGISTERED_CLASS: AtomicBool = AtomicBool::new(false);
78}
79
80fn try_register_class() -> windows::core::Result<()> {
81    if REGISTERED_CLASS.load(Ordering::Relaxed) {
82        return Ok(());
83    }
84
85    unsafe {
86        let instance = GetModuleHandleA(None)?;
87        let cursor = LoadCursorW(None, IDC_ARROW)?;
88
89        let mut style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE;
90
91        let enabled = DwmIsCompositionEnabled()?.as_bool();
92        if is_windows8_or_greater()? || !enabled {
93            style |= CS_OWNDC;
94        }
95
96        let window_class = w!("Win32DisplayClass");
97        let wc = WNDCLASSEXW {
98            cbSize: std::mem::size_of::<WNDCLASSEXW>() as u32,
99            hCursor: cursor,
100            hInstance: instance.into(),
101            lpszClassName: window_class,
102            style: CS_HREDRAW | CS_VREDRAW,
103            lpfnWndProc: Some(wndproc),
104            cbClsExtra: 0,
105            cbWndExtra: 0,
106            ..Default::default()
107        };
108
109        let atom = RegisterClassExW(&wc as *const _);
110        if atom == 0 {
111            return Err(std::io::Error::last_os_error().into());
112        }
113    }
114
115    REGISTERED_CLASS.store(true, Ordering::Relaxed);
116    Ok(())
117}
118
119#[derive(Debug)]
120pub struct DisplayWindowManager {
121    // Shouldn't really be needed
122    message_thread: Option<std::thread::JoinHandle<()>>,
123    should_exit: Arc<AtomicBool>,
124    hwnd: Sendable<HWND>,
125
126    x: i32,
127    y: i32,
128
129    width: u32,
130    height: u32,
131
132    scale: f32,
133
134    is_hidden: AtomicBool,
135
136    render_at_bottom: bool,
137
138    pub(super) obs_display: Option<Sendable<*mut obs_display_t>>,
139}
140
141impl DisplayWindowManager {
142    pub fn new(parent: Sendable<HWND>, x: i32, y: i32, width: u32, height: u32) -> anyhow::Result<Self> {
143        let (tx, rx) = oneshot::channel();
144
145        let should_exit = Arc::new(AtomicBool::new(false));
146        let tmp = should_exit.clone();
147
148        let parent = Mutex::new(Sendable(parent));
149        let message_thread = std::thread::spawn(move || {
150            let parent = parent.lock().unwrap().0.clone();
151            // We have to have the whole window creation stuff here as well so the message loop functions
152            let create = move || {
153                log::trace!("Registering class...");
154                try_register_class()?;
155                let win8 = is_windows8_or_greater()?;
156                let enabled = unsafe { DwmIsCompositionEnabled()?.as_bool() };
157
158                let mut window_style = WS_EX_TRANSPARENT;
159                if win8 && enabled {
160                    window_style |= WS_EX_COMPOSITED;
161                }
162
163                let instance = unsafe { GetModuleHandleW(PCWSTR::null())? };
164
165                let class_name = HSTRING::from("Win32DisplayClass");
166                let window_name = HSTRING::from("LibObsChildWindowPreview");
167                log::trace!("Creating window...");
168
169                log::debug!(
170                    "Creating window with x: {}, y: {}, width: {}, height: {}",
171                    x,
172                    y,
173                    width,
174                    height
175                );
176                let window = unsafe {
177                    // More at https://github.com/stream-labs/obs-studio-node/blob/4e19d8a61a4dd7744e75ce77624c664e371cbfcf/obs-studio-server/source/nodeobs_display.cpp#L170
178                    CreateWindowExW(
179                        WS_EX_LAYERED,
180                        &class_name,
181                        &window_name,
182                        WS_POPUP | WS_VISIBLE,
183                        x,
184                        y,
185                        width as i32,
186                        height as i32,
187                        None,
188                        None,
189                        Some(instance.into()),
190                        None,
191                    )?
192                };
193
194                log::trace!("HWND is {:?}", window);
195                if win8 || !enabled {
196                    log::trace!("Setting attributes alpha...");
197                    unsafe {
198                        SetLayeredWindowAttributes(window, COLORREF(0), 255, LWA_ALPHA)?;
199                    }
200                }
201
202                unsafe {
203                    log::trace!("Setting parent...");
204                    SetParent(window, Some(parent.0))?;
205                    log::trace!("Setting styles...");
206                    let mut style = GetWindowLongPtrW(window, GWL_STYLE);
207                    //TODO Check casts here
208                    style &= !(WS_POPUP.0 as isize);
209                    style |= WS_CHILD.0 as isize;
210
211                    SetWindowLongPtrW(window, GWL_STYLE, style);
212
213                    let mut ex_style = GetWindowLongPtrW(window, GWL_EXSTYLE);
214                    ex_style |= window_style.0 as isize;
215
216                    SetWindowLongPtrW(window, GWL_EXSTYLE, ex_style);
217                }
218
219                Result::<Sendable<HWND>, anyhow::Error>::Ok(Sendable(window))
220            };
221
222            let r = create();
223            let window = r.as_ref().ok().map(|r| r.0.clone());
224            tx.send(r).unwrap();
225            if window.is_none() {
226                return;
227            }
228            let window = window.unwrap();
229
230            log::trace!("Starting up message thread...");
231            let mut msg = MSG::default();
232            unsafe {
233                while !tmp.load(Ordering::Relaxed)
234                    && GetMessageW(&mut msg, Some(window), 0, 0).as_bool()
235                {
236                    //TODO check if this can really be ignored
237                    let _ = TranslateMessage(&msg);
238                    DispatchMessageW(&msg);
239                }
240            }
241
242            log::trace!("Exiting message thread...");
243        });
244
245        let window = rx.recv();
246        let window = window??;
247        Ok(Self {
248            x,
249            y,
250            width,
251            height,
252            scale: 1.0,
253            hwnd: window,
254            should_exit,
255            message_thread: Some(message_thread),
256            render_at_bottom: false,
257            is_hidden: AtomicBool::new(false),
258            obs_display: None,
259        })
260    }
261
262    pub fn get_child_handle(&self) -> HWND {
263        self.hwnd.0.clone()
264    }
265}
266
267impl Drop for DisplayWindowManager {
268    fn drop(&mut self) {
269        unsafe {
270            self.should_exit.store(true, Ordering::Relaxed);
271
272            log::trace!("Destroying window...");
273            let res = PostMessageW(Some(self.hwnd.0), WM_DESTROY_WINDOW, WPARAM(0), LPARAM(0));
274            if let Err(err) = res {
275                log::error!("Failed to post destroy window message: {:?}", err);
276            }
277
278            let thread = self.message_thread.take();
279            if let Some(thread) = thread {
280                log::trace!("Waiting for message thread to exit...");
281                thread.join().unwrap();
282            }
283        }
284    }
285}